package es.udima.geo.geoserver.rest;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import es.udima.geo.geoserver.entities.User;
import es.udima.geo.geoserver.entities.UserAccessError;
import es.udima.geo.geoserver.entities.UserAccessResult;
import es.udima.geo.geoserver.entities.UserLoginData;
import es.udima.geo.geoserver.entities.UserLoginError;
import es.udima.geo.geoserver.entities.UserLoginResult;
import es.udima.geo.geoserver.entities.UserSession;
import es.udima.geo.geoserver.entities.UserUpdateResult;
import es.udima.geo.geoserver.entities.VehicleType;

@Path("/users")
public class UserController {

    private static Map<String, User> users = new HashMap<String, User>();
    private static Map<String, User> usersByUsername = new HashMap<String, User>();
    private static Map<String, UserSession> userSessions = new HashMap<String, UserSession>();

    static {
	User u1 = new User();
	u1.setId("uid001");
	u1.setUserName("usuario001");
	u1.setPassword("usuario001");
	u1.setFirstName("David");
	u1.setLastName("Muñoz");
	u1.setVehicleType(VehicleType.STANDAR);
	u1.setFetchGpsLapseTime(2000);
	users.put(u1.getId(), u1);
	usersByUsername.put(u1.getUserName(), u1);

	User u2 = new User();
	u2.setId("uid002");
	u2.setUserName("usuario002");
	u2.setPassword("usuario002");
	u2.setFirstName("Luís");
	u2.setLastName("Serrano");
	u2.setFetchGpsLapseTime(2000);
	u2.setVehicleType(VehicleType.SPORT);
	users.put(u2.getId(), u2);
	usersByUsername.put(u2.getUserName(), u2);

	User u3 = new User();
	u3.setId("uid003");
	u3.setUserName("usuario003");
	u3.setPassword("usuario003");
	u3.setFirstName("Juan Luís");
	u3.setLastName("Rubio");
	u2.setFetchGpsLapseTime(1000);
	u3.setVehicleType(VehicleType.SUV);
	users.put(u3.getId(), u3);
	usersByUsername.put(u3.getUserName(), u3);
    }

    /**
     * 
     * @param params
     * @return
     */
    @POST
    @Path("/login")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public UserLoginResult loginUser(User user) {
	String userName = user.getUserName();
	String password = user.getPassword();

	UserLoginResult res = new UserLoginResult();

	if (userName.isEmpty()) {
	    res.addLoginError(UserLoginError.EMPTY_USERNAME);
	}

	if (password.isEmpty()) {
	    res.addLoginError(UserLoginError.EMPTY_PASSWORD);
	}

	if (res.isLoginSuccessful()) {
	    User userRead = usersByUsername.get(userName);
	    if (userRead == null) {
		res.addLoginError(UserLoginError.INCORRECT_USERNAME);
	    } else if (!password.equals(userRead.getPassword())) {
		res.addLoginError(UserLoginError.INCORRECT_PASSWORD);
	    } else {
		UserSession us = connectUser(userRead);
		res.setUserSession(us);
	    }
	}

	return res;
    }

    /**
     * 
     * @param params
     * @return
     */
    @POST
    @Path("/validateUserSession")
    @Produces(MediaType.APPLICATION_JSON)
    public UserAccessResult validateUserSession(UserSession userSession) {
	String userId = userSession.getUserId();
	String sessionToken = userSession.getSessionToken();

	UserAccessResult res = new UserAccessResult();

	if (!userId.isEmpty() && !sessionToken.isEmpty()) {
	    UserSession us = userSessions.get(userId);
	    if (us == null || !us.getSessionToken().equals(sessionToken)) {
		res.addAccessError(UserAccessError.USER_NOT_LOGGED);
	    } else if(calculateUserSessionHasExpired(us)) {
		res.addAccessError(UserAccessError.SESSION_EXPIRED);
	    }
	} else {
	    res.addAccessError(UserAccessError.UNKOWN_SESSION_DATA);
	}

	return res;
    }

    /**
     * 
     * @param params
     * @return
     */
    @POST
    @Path("/logout")
    public void logoutUser(UserSession userSession) {
	String userId = userSession.getUserId();
	String sessionToken = userSession.getSessionToken();
	disconnectUser(userId, sessionToken);
    }

    /**
     * 
     * @return
     */
    @GET
    @Path("/all")
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> readAllUsers() {
	List<User> us = new ArrayList<User>(users.values());
	for (User u : us) {
	    UserSession uSes = userSessions.get(u.getId());
	    if (uSes != null) {
		u.setConnected(true);
		u.setLastConnection(uSes.getUser().getLastConnection());
	    } else {
		u.setConnected(false);
	    }
	}

	return us;
    }

    /**
     * 
     * @param userId
     * @return
     */
    @GET
    @Path("/{userId}")
    @Produces(MediaType.APPLICATION_JSON)
    public User readUser(@PathParam("userId") String userId) {
	User u = users.get(userId);
	return u;
    }

    /**
     * 
     * @param params
     * @return
     */
    @POST
    @Path("/updateUserData")
    @Produces(MediaType.APPLICATION_JSON)
    public UserUpdateResult updateUserData(User user) {

	UserUpdateResult res = new UserUpdateResult();

	return res;
    }

    /*------------------------------------------------
     * private methods to be moved to another specific classes
     * ------------------------------------------------
     */

    /**
     * 
     * @return
     */
    private String generateSessionToken() {
	// TODO implements a real random session token generator
	SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
	Date now = new Date();
	return sdf.format(now);
    }

    /**
     * 
     * @param user
     */
    private UserSession connectUser(User user) {
	user.setConnected(true);
	user.setLastConnection(new Date());
	UserSession us = new UserSession();
	us.setUserId(user.getId());
	us.setSessionToken(generateSessionToken());
	us.setUser(user);
	userSessions.put(us.getUserId(), us);
	return us;
    }

    /**
     * 
     * @param user
     */
    private void disconnectUser(String userId, String sessionToken) {
	UserSession us = userSessions.get(userId);
	if (us != null && sessionToken.equals(us.getSessionToken())) {
	    userSessions.remove(userId);
	}
    }
    
    /**
     * 
     * @param userSession
     * @return
     */
    private boolean calculateUserSessionHasExpired(UserSession userSession) {
	//TODO calculate session expiration
	return false;
    }
}
